#pragma once
#include <corecrt.h>
#include <cstdio>
#include <mutex>
#include <thread>
#include <atomic>
#include <cassert>
#include <cstddef>
#include <iostream>
#include <memory>

using std::cout;
using std::endl;
using namespace std;

namespace test
{
template<class T>
struct default_deleter
{
    void operator()(const T* ptr) { delete ptr; };
};

template<class T>
struct array_deleter
{
    void operator()(const T* ptr) { delete[] ptr; };
};

struct file_closer
{
    void oeprator()(FILE* ptr) { fclose(ptr);  }
};

template<class T>
class auto_ptr
{
public:
    auto_ptr(T* ptr)
        : _ptr(ptr)
    {}

    auto_ptr(auto_ptr& p)
        : _ptr(p._ptr)
    {
        p._ptr = nullptr;
    }

    ~auto_ptr()
    {
        cout << "delete: " << _ptr << endl;
        delete _ptr;
    }

    T& operator*()
    {
        return *_ptr;
    }

    T* operator->()
    {
        return _ptr;
    }

private:
    T* _ptr;
};


template<class T, class Deleter = default_deleter<T>>
class unique_ptr
{
public:
    unique_ptr(T* ptr) : _ptr(ptr)
    {}

// private:
//     unique_ptr(unique_ptr& p);
//     unique_ptr& operator=(unique_ptr& p);

public:
    unique_ptr(unique_ptr& p) = delete;
    unique_ptr& operator=(unique_ptr& p) = delete;

    ~unique_ptr()
    {
        cout << "delete: " << _ptr << endl;
        Deleter()(_ptr);
    }

    T& operator*() { return *_ptr; }

    T* operator->() { return _ptr; }

private:
    T* _ptr;
};

template<class T, class Deleter = default_deleter<T>>
class shared_ptr
{
private:
    void add_ref()
    {
        _pmtx->lock();
        ++(*_pcnt);
        _pmtx->unlock();
    }

    void release()
    {
        _pmtx->lock();

        bool flag = false;
        if (--(*_pcnt) == 0)
        {
            cout << "delete: " << _ptr << endl;
            Deleter()(_ptr);
            delete _pcnt;
            _ptr = nullptr;
            _pcnt = nullptr;

            flag = true;
        }

        _pmtx->unlock();

        if (flag)
        {
            delete _pmtx;
            _pmtx = nullptr;
        }
    }

public:
    shared_ptr(T* ptr = nullptr)
        : _ptr(ptr)
        , _pcnt(new int(1))
        , _pmtx(new mutex)
    {}

    shared_ptr(const shared_ptr& p)
        : _ptr(p._ptr)
        , _pcnt(p._pcnt)
        , _pmtx(p._pmtx)
    {
        add_ref();
    }

    shared_ptr& operator=(const shared_ptr& p)
    {
        if (_ptr != p._ptr)
        {
            release();

            _ptr = p._ptr;
            _pcnt = p._pcnt;
            _pmtx = p._pmtx;
            add_ref();
        }

        return *this;
    }

    ~shared_ptr()
    {
        release();
    }

    T& operator*()
    {
        return *_ptr;
    }

    T* operator->()
    {
        return _ptr;
    }

    T* get() const
    {
        return _ptr;
    }

    const size_t use_count() const
    {
        return *_pcnt;
    }

private:
    T* _ptr;
    int* _pcnt;
    mutex* _pmtx;
};

template<class T>
class weak_ptr
{
public:
    weak_ptr(T* ptr = nullptr)
        : _ptr(ptr)
    {}

    weak_ptr(const weak_ptr& wp)
        : _ptr(wp._ptr)
    {}

    weak_ptr(const shared_ptr<T>& sp)
        : _ptr(sp.get())
    {}

    weak_ptr& operator=(const weak_ptr& wp)
    {
        _ptr = wp._ptr;
        return *this;
    }

    weak_ptr& operator=(const shared_ptr<T>& sp)
    {
        _ptr = sp.get();
        return *this;
    }

    T* get() const
    {
        return _ptr;
    }

private:
    T* _ptr;
};

void test_auto_ptr()
{
    auto_ptr<int> sp1(new int);
    auto_ptr<int> sp2(sp1);
}

void test_unique_ptr()
{
    unique_ptr<int> sp1(new int);
    //unique_ptr<int> sp2(sp1);
}

void test_shared_ptr()
{
    shared_ptr<int> sp1(new int);
    shared_ptr<int> sp2(sp1);
    shared_ptr<int> sp3(new int);
    shared_ptr<int> sp4(sp3);

    sp2 = sp1;
    sp3 = sp1;
    sp4 = sp1;
}

struct date
{
    int _year = 0;
    int _month = 0;
    int _day = 0;

    ~date()
    {}
};

void test_thread_safe(shared_ptr<date>& sp, size_t n, mutex& mtx)
{
    cout << sp.get() << endl;

    for (size_t i = 0; i < n; i++)
    {
        shared_ptr<date> cp(sp);

        lock_guard<mutex> lock(mtx);
        cp->_year++;
        cp->_month++;
        cp->_day++;
    }
}

void test_shared_ptr_2()
{
    shared_ptr<date> sp(new date);
    cout << sp.get() << endl;

    size_t n = 100000;
    mutex mtx;
    thread t1(test_thread_safe, std::ref(sp), n, std::ref(mtx));
    thread t2(test_thread_safe, std::ref(sp), n, std::ref(mtx));

    t1.join();
    t2.join();

    cout << sp.use_count() << endl;
    cout << sp->_year << endl;
    cout << sp->_month << endl;
    cout << sp->_day << endl;
}

struct list_node
{
    // list_node* _prev;
    // list_node* _next;
    // shared_ptr<list_node> _prev;
    // shared_ptr<list_node> _next;
    weak_ptr<list_node> _prev;
    weak_ptr<list_node> _next;
    int _val;
 
    ~list_node()
    {
        cout << "~list_node()" << endl;
    }
};

void test_shared_ptr_cycle_ref()
{
    shared_ptr<list_node> n1(new list_node);
    shared_ptr<list_node> n2(new list_node);

    // n1->_next = n2.get();
    // n2->_prev = n1.get();

    n1->_next = n2;
    n2->_prev = n1;
}

void test_delete_functor()
{
    shared_ptr<date> sp1(new date);
    shared_ptr<date, array_deleter<date>> sp2(new date[3]);
    shared_ptr<FILE, file_closer> sp3(fopen("test.cc", "r"));

}

}
